﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using APICommon;
using DataProvider;

namespace DataBuilder
{
    public class AssemblyProcessor : MarshalByRefObject
    {
        private void ReportProgress(Action<Progress> progressAction, string message)
        {
            if (progressAction != null)
            {
                progressAction(Progress.GetProgress(message));
            }
        }

        internal Result Process(string file, Action<Progress> progressAction)
        {
            List<string> errors = new List<string>();

            System.Diagnostics.Debug.WriteLine("Started " + file);

            ReportProgress(progressAction, "Processing " + Path.GetFileName(file));

            try
            {

                Assembly assembly = Assembly.LoadFrom(file);
                Type[] types = assembly.GetTypes();
                List<SageField> fields = new List<SageField>();
                List<SageMethod> properties = new List<SageMethod>();
                List<SageMethod> methods = new List<SageMethod>();
                Version version = new Version(FileVersionInfo.GetVersionInfo(assembly.Location).ProductVersion);
                file = System.IO.Path.GetFileName(file);

                SageVersion sageVersion = null;
                using (var db = new APIDBContext())
                {
                    sageVersion = db.Versions.FirstOrDefault(s => s.MajorVersion == version.Major
                        && s.MinorVersion == version.Minor
                        && s.Build == version.Build
                        && s.Revision == version.Revision);
                    if (sageVersion == null)
                    {
                        sageVersion = new SageVersion(version);
                        db.Versions.Add(sageVersion);
                        db.SaveChanges();

                        sageVersion = db.Versions.FirstOrDefault(s => s.MajorVersion == version.Major
                        && s.MinorVersion == version.Minor
                        && s.Build == version.Build
                        && s.Revision == version.Revision);
                    }
                }

                foreach (Type type in types)
                {
                    if (!type.IsPublic) continue;
                    DataProcessor.Process(file, type, fields, properties, methods, sageVersion);
                }

                using (var db = new APIDBContext())
                {
                    bool retry = true;
                    string error = string.Empty;
                    int retries = 0;
                    while (retry && retries < 3)
                    {
                        try
                        {
                            db.Configuration.AutoDetectChangesEnabled = false;
                            System.Diagnostics.Debug.WriteLine("Processing " + file);
                            // Remove from database
                            int count = 1;
                            while (count > 0)
                            {
                                count = db.Fields.RemoveRange(db.Fields
                                    .Where(s => s.Assembly == file
                                        && s.VersionId == sageVersion.Id).Take(1000)).Count();
                                var methodsToRemove = db.Methods.Where(s => s.Assembly == file
                                        && s.VersionId == sageVersion.Id).Take(1000);
                                var argumentsToRemove = methodsToRemove./*Where(s => s.Arguments != null)*/SelectMany(s => s.Arguments);//.Select(s => s.ArgumentId).ToList();
                                db.Arguments.RemoveRange(argumentsToRemove);
                                //HashSet<int> hs = new HashSet<int>(argumentsToRemove);
                                //db.Arguments.RemoveRange(db.Arguments.Where(s=>hs.Contains(s.ArgumentId)));
                                count += db.Methods.RemoveRange(methodsToRemove).Count();
                                db.SaveChanges();
                            }

                            // Add to database
                            fields.ForEach(s => db.Fields.Add(s));
                            properties.ForEach(s => db.Methods.Add(s));
                            methods.ForEach(s => db.Methods.Add(s));
                            db.SaveChanges();
                            retry = false;
                        }
                        catch (Exception ex)
                        {
                            System.Diagnostics.Debug.WriteLine(string.Format("Exception: {0}", ex.Message));
                            error = ex.Message;
                            retries++;
                        }
                    }
                    if (retry == true)
                    {
                        // This failed even after 3 attempts
                        errors.Add(error);
                    }
                }
            }
            catch (Exception ex)
            {
                errors.Add(ex.Message);
            }
            System.Diagnostics.Debug.WriteLine("Completed " + file);

            return new Result() { Errors = errors };
        }
        /*
        private static void Process(string file, Type type, List<SageField> fields, List<SageMethod> properties, List<SageMethod> methods, SageVersion version)
        {
            try
            {
                ProcessMethods(file, type, methods, version);
                ProcessProperties(file, type, properties, version);
                ProcessFields(file, type, fields, version);
            }
            catch { }
        }

        private static void ProcessFields(string file, Type type, List<SageField> fieldList, SageVersion version)
        {
            FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            foreach (FieldInfo info in fields)
            {
                fieldList.Add(new SageField(file, info, version));
            }
        }

        private static void ProcessProperties(string file, Type type, List<SageMethod> propertyList, SageVersion version)
        {
            PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            foreach (PropertyInfo info in properties)
            {
                // Only interested in the properties defined in this type
                if (type != info.DeclaringType) continue;
                propertyList.Add(new SageMethod(file, info, version));
            }
        }

        private static void ProcessMethods(string file, Type type, List<SageMethod> methodList, SageVersion version)
        {
            MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            foreach (MethodInfo info in methods)
            {
                // Ignore the get_ set_ methods representing properties
                if (info.IsSpecialName && (info.Name.StartsWith("set_") || info.Name.StartsWith("get_"))) continue;
                // Only interested in the methods defined in this type
                if (type != info.DeclaringType) continue;
                methodList.Add(new SageMethod(file, info, version));
            }
        }
        */
    }
}
